/*
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "pyWrappers.h"


#define PYJSOBJECT_PREP_CALL(err) \
    JSContext *cx = pyRuntime::sPyRuntime->GetJSContext(); \
    if (!cx) { \
        return (err); \
    } \
    JSAutoRequest ar(cx); \
    JSAutoErrorReporter aer(cx); \
    JSAutoCompartment ac(cx, self->jsobj);


typedef struct PyJSObject : PyObject {
    JSObject *jsobj;
    PyObject *weakrefslist;
} PyJSObject;

typedef struct PyJSIterator : PyJSObject {
    int32_t idx; // -1 for item, 0 for key, 1 for value
    Py_ssize_t len; // for __length_hint__
} PyJSIterator;

typedef struct PyJSFunction : PyJSObject {
    JSObject *that;
} PyJSFunction;


/* fwd */
static PyObject *
PyJSObject_Create(PyTypeObject *type, JSObject *jsobj);

static bool
PyJSFunction_Check(PyObject *pyobj);

static bool
PyJSFunction_SetMethod(PyJSFunction *self, JSObject *that);


/*******************************************************************************
* JSAutoErrorReporter
*******************************************************************************/

static void
ReportToPython(JSContext *cx, const char *message, JSErrorReport *report)
{
    nsCString aFilename;
    if (report->filename &&
        !pyRuntime::sPyRuntime->ConvertURL(report->filename, aFilename)) {
        return;
    }
    if (JSREPORT_IS_WARNING(report->flags)) {
        if (aFilename.IsEmpty()) {
            PyErr_WarnEx(pyRuntime::sPyRuntime->mJSWarning, message, 1);
        }
        else {
            PyErr_WarnExplicit(pyRuntime::sPyRuntime->mJSWarning, message,
                               aFilename.get(), report->lineno, NULL, NULL);
        }
    }
    else {
        PyErr_SetString(pyRuntime::sPyRuntime->mJSError, message);
        if (!aFilename.IsEmpty()) {
            PyErr_SyntaxLocation(aFilename.get(), report->lineno);
        }
    }
}


class JSAutoErrorReporter
{
public:
    JSAutoErrorReporter(JSContext *cx)
        : mContext(cx),
          mErrorReporter(NULL)
    {
        mErrorReporter = JS_SetErrorReporter(mContext, ReportToPython);
    }

    ~JSAutoErrorReporter()
    {
        JS_ReportPendingException(mContext);
        JS_SetErrorReporter(mContext, mErrorReporter);
    }

private:
    JSContext *mContext;
    JSErrorReporter mErrorReporter;
};


/*******************************************************************************
 utils
*******************************************************************************/

static int
PyObject_GenericHasAttr(PyObject *v, PyObject *name)
{
    PyObject *res = PyObject_GenericGetAttr(v, name);
    if (res != NULL) {
        Py_DECREF(res);
        return 1;
    }
    PyErr_Clear();
    return 0;
}


static JSBool
JSValue_ProtoCheck(JSContext *cx, jsval val, JSProtoKey key)
{
    JSBool result = JS_FALSE;
    JSObject *jsproto = NULL;
    if (JS_GetClassObject(cx, JS_GetGlobalObject(cx), key, &jsproto) &&
        jsproto) {
        JS_HasInstance(cx, jsproto, val, &result);
    }
    if (!result && JS_IsExceptionPending(cx)) {
        JS_ClearPendingException(cx);
    }
    return result;
}

#define JSIterator_Check(cx, jsobj) \
    JSValue_ProtoCheck(cx, OBJECT_TO_JSVAL(jsobj), JSProto_Iterator)

#define JSMap_Check(cx, jsobj) \
    JSValue_ProtoCheck(cx, OBJECT_TO_JSVAL(jsobj), JSProto_Map)


static void
JSArgs_Destroy(JSContext *cx, jsval *argv, unsigned argc)
{
    unsigned i;

    if (argv) {
        if (argc) {
            for (i = argc; i-- > 0;) {
                JS_RemoveValueRoot(cx, &(argv[i]));
            }
        }
        JS_free(cx, argv);
    }
}


static jsval *
PyArgs_AsJSArgs(JSContext *cx, PyObject *pyargs, unsigned *argc)
{
    if (!pyargs) {
        return NULL; // not an error
    }
    Py_ssize_t size = PySequence_Fast_GET_SIZE(pyargs);
    if (size <= 0) {
        return NULL; // error only if size < 0
    }
    jsval *argv = (jsval *)JS_malloc(cx, size * sizeof(jsval));
    if (!argv) {
        return NULL;
    }
    for (Py_ssize_t i = 0; i < size; i++) {
        argv[i] = PyObject_AsJSVal(cx, PySequence_Fast_GET_ITEM(pyargs, i));
        if (JSVAL_IS_VOID(argv[i]) || !JS_AddValueRoot(cx, &(argv[i]))) {
            JSArgs_Destroy(cx, argv, i);
            return NULL;
        }
    }
    *argc = size;
    return argv;
}


static JSBool
PyJSObject_CallMethodString(JSContext *cx, PyJSObject *self, const char *name,
                            jsval *rv, PyObject *pyargs)
{
    JSBool result = JS_FALSE;
    unsigned argc = 0;
    jsval *argv = PyArgs_AsJSArgs(cx, pyargs, &argc);
    if (!argv && PyErr_Occurred()) {
        return false;
    }
    result = JS_CallFunctionName(cx, self->jsobj, name, argc, argv, rv);
    if (argv) {
        JSArgs_Destroy(cx, argv, argc);
    }
    return result;
}


/*static JSBool
PyJSObject_VaCallMethodString(JSContext *cx, PyJSObject *self, const char *name,
                              jsval *rv, const char *format, ...)
{
    PyObject *pyargs = NULL;
    if (format) {
        va_list vargs;
        va_start(vargs, format);
        pyargs = Py_VaBuildValue(format, vargs);
        va_end(vargs);
        if (!pyargs) {
            return JS_FALSE;
        }
    }
    JSBool result = PyJSObject_CallMethodString(cx, self, name, rv, pyargs);
    Py_XDECREF(pyargs);
    return result;
}*/


/*static int
PyJSObject_HasAttrString(JSContext *cx, PyJSObject *self, const char *name)
{
    JSBool has_attr;
    if (!JS_HasProperty(cx, self->jsobj, name, &has_attr)) {
        return -1;
    }
    return has_attr;
}*/


static PyObject *
PyJSObject_GetAttrString(JSContext *cx, PyJSObject *self, const char *name)
{
    jsval rv;
    if (!JS_GetProperty(cx, self->jsobj, name, &rv)) {
        return NULL;
    }
    if (JSVAL_IS_VOID(rv)) {
        PyErr_Format(PyExc_AttributeError, "%S has no attribute '%s'", self, name);
        return NULL;
    }
    PyObject *pyresult = PyObject_FromJSVal(cx, rv);
    if (pyresult && PyJSFunction_Check(pyresult)) {
        if (!PyJSFunction_SetMethod((PyJSFunction *)pyresult, self->jsobj)) {
            Py_CLEAR(pyresult);
        }
    }
    return pyresult;
}


static int
PyJSObject_SetAttrString(JSContext *cx, PyJSObject *self, const char *name,
                         PyObject *pyvalue)
{
    jsval argv = PyObject_AsJSVal(cx, pyvalue);
    if (JSVAL_IS_VOID(argv)) {
        return -1;
    }
    JS::AutoValueRooter avr(cx, argv);
    return JS_SetProperty(cx, self->jsobj, name, &argv) ? 0 : -1;
}


static int
PyJSObject_DelAttrString(JSContext *cx, PyJSObject *self, const char *name)
{
    return JS_DeleteProperty(cx, self->jsobj, name) ? 0 : -1;
}


/*******************************************************************************
 PyJSObjectType
*******************************************************************************/

/* PyJSObjectType.tp_dealloc */
static void
PyJSObject_tp_dealloc(PyJSObject *self)
{
    if (self->weakrefslist) {
        PyObject_ClearWeakRefs(self);
    }

    if (self->jsobj) {
        pyRuntime::sPyRuntime->DropJSObject(&self->jsobj);
        self->jsobj = NULL;
    }

    Py_TYPE(self)->tp_free(self);
}


/* PyJSObjectType.tp_repr */
static PyObject *
PyJSObject_tp_repr(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    jsval argv = OBJECT_TO_JSVAL(self->jsobj);
    JS::AutoValueRooter avr(cx, argv);
    JSString *string = JS_ValueToSource(cx, argv);
    if (!string) {
        return NULL;
    }
    PyObject *result = NULL;
    PyObject *repr = JSString_Decode(cx, string);
    if (repr) {
        result = PyUnicode_FromFormat("<%s at %p wrapping: [%U at %p]>",
                                      Py_TYPE(self)->tp_name, self, repr,
                                      self->jsobj);
        Py_DECREF(repr);
    }
    return result;
}


/* PyJSObjectType.tp_hash */
static Py_hash_t
PyJSObject_tp_hash(PyJSObject *self)
{
    return _Py_HashPointer(self->jsobj);
}


/* PyJSObjectType.tp_str */
static PyObject *
PyJSObject_tp_str(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    jsval argv = OBJECT_TO_JSVAL(self->jsobj);
    JS::AutoValueRooter avr(cx, argv);
    JSString *string = JS_ValueToString(cx, argv);
    if (!string) {
        return NULL;
    }
    return JSString_Decode(cx, string);
}


/* PyJSObjectType.tp_getattro */
static PyObject *
PyJSObject_tp_getattro(PyJSObject *self, PyObject *pyname)
{
    if (PyObject_GenericHasAttr(self, pyname)) {
        return PyObject_GenericGetAttr(self, pyname);
    }
    const char *name = PyUnicode_AsUTF8(pyname);
    if (!name) {
        return NULL;
    }
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetAttrString(cx, self, name);
}


/* PyJSObjectType.tp_setattro */
static int
PyJSObject_tp_setattro(PyJSObject *self, PyObject *pyname, PyObject *pyvalue)
{
    if (PyObject_GenericHasAttr(self, pyname)) {
        return PyObject_GenericSetAttr(self, pyname, pyvalue);
    }
    const char *name = PyUnicode_AsUTF8(pyname);
    if (!name) {
        return -1;
    }
    PYJSOBJECT_PREP_CALL(-1)
    if (pyvalue) {
        return PyJSObject_SetAttrString(cx, self, name, pyvalue);
    }
    return PyJSObject_DelAttrString(cx, self, name);
}


/* PyJSObjectType.tp_richcompare */
static PyObject *
PyJSObject_tp_richcompare(PyJSObject *self, PyObject *other, int op)
{
    int eq = 0;

    if (PyJSObject_Check(other)) {
        eq = (self->jsobj == ((PyJSObject *)other)->jsobj);
        switch (op) {
            case Py_EQ:
                return PyBool_FromLong(eq);
            case Py_NE:
                return PyBool_FromLong(!eq);
            default:
                PyErr_SetString(PyExc_TypeError, "unorderable types");
                return NULL;
        }
    }
    Py_RETURN_NOTIMPLEMENTED;
}


/* PyJSObjectType */
static PyTypeObject PyJSObjectType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "PyJSObject",                             /*tp_name*/
    sizeof(PyJSObject),                       /*tp_basicsize*/
    0,                                        /*tp_itemsize*/
    (destructor)PyJSObject_tp_dealloc,        /*tp_dealloc*/
    0,                                        /*tp_print*/
    0,                                        /*tp_getattr*/
    0,                                        /*tp_setattr*/
    0,                                        /*tp_compare*/
    (reprfunc)PyJSObject_tp_repr,             /*tp_repr*/
    0,                                        /*tp_as_number*/
    0,                                        /*tp_as_sequence*/
    0,                                        /*tp_as_mapping*/
    (hashfunc)PyJSObject_tp_hash,             /*tp_hash*/
    0,                                        /*tp_call*/
    (reprfunc)PyJSObject_tp_str,              /*tp_str*/
    (getattrofunc)PyJSObject_tp_getattro,     /*tp_getattro*/
    (setattrofunc)PyJSObject_tp_setattro,     /*tp_setattro*/
    0,                                        /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                       /*tp_flags*/
    0,                                        /*tp_doc*/
    0,                                        /*tp_traverse*/
    0,                                        /*tp_clear*/
    (richcmpfunc)PyJSObject_tp_richcompare,   /*tp_richcompare*/
    offsetof(PyJSObject, weakrefslist),       /*tp_weaklistoffset*/
};


/*******************************************************************************
 PyJSIteratorType
*******************************************************************************/

/* PyJSIteratorType.tp_iternext */
static PyObject *
PyJSIterator_tp_iternext(PyJSIterator *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    jsval pair;
    if (!JS_CallFunctionName(cx, self->jsobj, "next", 0, NULL, &pair)) {
        jsval exn;
        if (JS_IsExceptionPending(cx) && JS_GetPendingException(cx, &exn)) {
            JS_ClearPendingException(cx);
            if (JSValue_ProtoCheck(cx, exn, JSProto_StopIteration)) {
                PyErr_SetNone(PyExc_StopIteration);
            }
            else {
                JS_SetPendingException(cx, exn);
            }
        }
        return NULL;
    }
    if (self->idx >= 0) {
        jsval item;
        if (!JS_GetElement(cx, JSVAL_TO_OBJECT(pair), self->idx, &item)) {
            return NULL;
        }
        return PyObject_FromJSVal(cx, item);
    }
    return PyObject_FromJSVal(cx, pair);
}


/* PyJSIterator.__length_hint__() */
PyDoc_STRVAR(PyJSIterator_Length_doc,
"Private method returning an estimate of len(list(self)).");

static PyObject *
PyJSIterator_Length(PyJSIterator *self)
{
    return PyLong_FromSsize_t(self->len);
}


/* PyJSIteratorType.tp_methods */
static PyMethodDef PyJSIterator_tp_methods[] = {
    {"__length_hint__", (PyCFunction)PyJSIterator_Length,
     METH_NOARGS, PyJSIterator_Length_doc},
    {NULL}  /* Sentinel */
};


/* PyJSIteratorType */
static PyTypeObject PyJSIteratorType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "PyJSIterator",                           /*tp_name*/
    sizeof(PyJSIterator),                     /*tp_basicsize*/
    0,                                        /*tp_itemsize*/
    0,                                        /*tp_dealloc*/
    0,                                        /*tp_print*/
    0,                                        /*tp_getattr*/
    0,                                        /*tp_setattr*/
    0,                                        /*tp_compare*/
    0,                                        /*tp_repr*/
    0,                                        /*tp_as_number*/
    0,                                        /*tp_as_sequence*/
    0,                                        /*tp_as_mapping*/
    0,                                        /*tp_hash */
    0,                                        /*tp_call*/
    0,                                        /*tp_str*/
    0,                                        /*tp_getattro*/
    0,                                        /*tp_setattro*/
    0,                                        /*tp_as_buffer*/
    0,                                        /*tp_flags*/
    0,                                        /*tp_doc*/
    0,                                        /*tp_traverse*/
    0,                                        /*tp_clear*/
    0,                                        /*tp_richcompare*/
    0,                                        /*tp_weaklistoffset*/
    PyObject_SelfIter,                        /*tp_iter*/
    (iternextfunc)PyJSIterator_tp_iternext,   /*tp_iternext*/
    PyJSIterator_tp_methods,                  /*tp_methods*/
    0,                                        /*tp_members*/
    0,                                        /*tp_getset*/
    0,/*&PyJSObjectType,*/                          /*tp_base*/
};


static PyObject *
PyJSObject_GetIter(JSContext *cx, PyJSObject *self, int32_t idx)
{
    Py_ssize_t len = PyObject_Length(self);
    JSObject *glob = JS_GetGlobalObject(cx);
    JSObject *jsproto = NULL;
    if (len < 0 ||
        !JS_GetClassObject(cx, glob, JSProto_Iterator, &jsproto) ||
        !jsproto) {
        return NULL;
    }
    jsval argv = OBJECT_TO_JSVAL(self->jsobj);
    JS::AutoValueRooter avr(cx, argv);
    JSObject *jsiter = JS_New(cx, jsproto, 1, &argv);
    if (!jsiter) {
        return NULL;
    }
    // iterators will not go in the cache
    //PyObject *pyresult = PyJSObject_New(cx, jsiter);
    PyObject *pyresult = PyJSObject_Create(&PyJSIteratorType, jsiter);
    //PyObject *pyresult = PyJSObject_NewOrUsed(cx, jsiter);
    if (pyresult) {
        PyJSIterator *pyiter = (PyJSIterator *)pyresult;
        pyiter->idx = idx;
        pyiter->len = len;
    }
    return pyresult;
}


/*******************************************************************************
 PyJSMapType
*******************************************************************************/

/* PyJSMap_tp_as_sequence.sq_contains */
static int
PyJSMap_Contains(PyJSObject *self, PyObject *pykey)
{
    PYJSOBJECT_PREP_CALL(-1)
    jsval rv;
    jsval argv = PyObject_AsJSVal(cx, pykey);
    if (JSVAL_IS_VOID(argv)) {
        return -1;
    }
    JS::AutoValueRooter avr(cx, argv);
    if (!JS_CallFunctionName(cx, self->jsobj, "has", 1, &argv, &rv)) {
        return -1;
    }
    return (JSVAL_TO_BOOLEAN(rv)) ? 1 : 0;
}


/* PyJSMapType.tp_as_sequence */
static PySequenceMethods PyJSMap_tp_as_sequence = {
    0,                                        /*sq_length*/
    0,                                        /*sq_concat*/
    0,                                        /*sq_repeat*/
    0,                                        /*sq_item*/
    0,                                        /*was_sq_slice*/
    0,                                        /*sq_ass_item*/
    0,                                        /*was_sq_ass_slice*/
    (objobjproc)PyJSMap_Contains,             /*sq_contains*/
    0,                                        /*sq_inplace_concat*/
    0                                         /*sq_inplace_repeat*/
};


/* PyJSMap_tp_as_mapping.mp_length */
static Py_ssize_t
PyJSMap_Length(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(-1)
    jsval rv;
    if (!JS_CallFunctionName(cx, self->jsobj, "size", 0, NULL, &rv)) {
        return -1;
    }
    uint32_t len;
    if (!JS_ValueToECMAUint32(cx, rv, &len)) {
        return -1;
    }
#if SIZEOF_SIZE_T == 4
    if (len > PY_SSIZE_T_MAX) {
        PyErr_SetString(PyExc_OverflowError, "length too big for Python");
        return -1;
    }
#endif
    return len;
}


/* PyJSMap_tp_as_mapping.mp_subscript */
static PyObject *
PyJSMap_GetItem(PyJSObject *self, PyObject *pykey)
{
    PYJSOBJECT_PREP_CALL(NULL)
    jsval rv;
    jsval argv = PyObject_AsJSVal(cx, pykey);
    if (JSVAL_IS_VOID(argv)) {
        return NULL;
    }
    JS::AutoValueRooter avr(cx, argv);
    if (!JS_CallFunctionName(cx, self->jsobj, "get", 1, &argv, &rv)) {
        return NULL;
    }
    if (JSVAL_IS_VOID(rv)) {
        PyErr_SetObject(PyExc_KeyError, pykey);
        return NULL;
    }
    return PyObject_FromJSVal(cx, rv);
}


/* PyJSMap_tp_as_mapping.mp_ass_subscript */
static int
PyJSMap_SetItem(PyJSObject *self, PyObject *pykey, PyObject *pyvalue)
{
    PYJSOBJECT_PREP_CALL(-1)
    jsval rv;
    if (pyvalue) {
        jsval argv[2];
        argv[0] = PyObject_AsJSVal(cx, pykey);
        argv[1] = PyObject_AsJSVal(cx, pyvalue);
        if (JSVAL_IS_VOID(argv[0]) || JSVAL_IS_VOID(argv[1])) {
            return -1;
        }
        JS::AutoArrayRooter aar(cx, 2, argv);
        return JS_CallFunctionName(cx, self->jsobj, "set", 2, argv, &rv) ? 0 : -1;
    }
    jsval argv = PyObject_AsJSVal(cx, pykey);
    if (JSVAL_IS_VOID(argv)) {
        return -1;
    }
    JS::AutoValueRooter avr(cx, argv);
    if (!JS_CallFunctionName(cx, self->jsobj, "delete", 1, &argv, &rv)) {
        return -1;
    }
    if (!JSVAL_TO_BOOLEAN(rv)) {
        PyErr_SetObject(PyExc_KeyError, pykey);
        return -1;
    }
    return 0;
}


/* PyJSMapType.tp_as_mapping */
static PyMappingMethods PyJSMap_tp_as_mapping = {
    (lenfunc)PyJSMap_Length,                  /*mp_length*/
    (binaryfunc)PyJSMap_GetItem,              /*mp_subscript*/
    (objobjargproc)PyJSMap_SetItem            /*mp_ass_subscript*/
};


#if 0
/* PyJSMapType.tp_iter */
static PyObject *
PyJSMap_tp_iter(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetIter(cx, self, 0);
}


/* PyJSMap.keys() */
PyDoc_STRVAR(PyJSMap_Keys_doc,
"keys()");

static PyObject *
PyJSMap_Keys(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetIter(cx, self, 0);
}


/* PyJSMap.values() */
PyDoc_STRVAR(PyJSMap_Values_doc,
"values()");

static PyObject *
PyJSMap_Values(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetIter(cx, self, 1);
}


/* PyJSMap.items() */
PyDoc_STRVAR(PyJSMap_Items_doc,
"items()");

static PyObject *
PyJSMap_Items(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetIter(cx, self, -1);
}


/* PyJSMapType.tp_methods */
static PyMethodDef PyJSMap_tp_methods[] = {
    {"keys", (PyCFunction)PyJSMap_Keys, METH_NOARGS, PyJSMap_Keys_doc},
    {"values", (PyCFunction)PyJSMap_Values, METH_NOARGS, PyJSMap_Values_doc},
    {"items", (PyCFunction)PyJSMap_Items, METH_NOARGS, PyJSMap_Items_doc},
    {NULL}  /* Sentinel */
};
#endif


/* PyJSMapType */
static PyTypeObject PyJSMapType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "PyJSMap",                                /*tp_name*/
    sizeof(PyJSObject),                       /*tp_basicsize*/
    0,                                        /*tp_itemsize*/
    0,                                        /*tp_dealloc*/
    0,                                        /*tp_print*/
    0,                                        /*tp_getattr*/
    0,                                        /*tp_setattr*/
    0,                                        /*tp_compare*/
    0,                                        /*tp_repr*/
    0,                                        /*tp_as_number*/
    &PyJSMap_tp_as_sequence,                  /*tp_as_sequence*/
    &PyJSMap_tp_as_mapping,                   /*tp_as_mapping*/
    0,                                        /*tp_hash */
    0,                                        /*tp_call*/
    0,                                        /*tp_str*/
    0,                                        /*tp_getattro*/
    0,                                        /*tp_setattro*/
    0,                                        /*tp_as_buffer*/
    0,                                        /*tp_flags*/
    0,                                        /*tp_doc*/
    0,                                        /*tp_traverse*/
    0,                                        /*tp_clear*/
    0,                                        /*tp_richcompare*/
    0,                                        /*tp_weaklistoffset*/
    0,/*(getiterfunc)PyJSMap_tp_iter,*/             /*tp_iter*/
    0,                                        /*tp_iternext*/
    0,/*PyJSMap_tp_methods,*/                       /*tp_methods*/
    0,                                        /*tp_members*/
    0,                                        /*tp_getset*/
    0,/*&PyJSObjectType,*/                          /*tp_base*/
};


/*******************************************************************************
 PyJSArrayType
*******************************************************************************/

static Py_ssize_t
_PyJSArray_Length(JSContext *cx, PyJSObject *self)
{
    uint32_t len;
    if (!JS_GetArrayLength(cx, self->jsobj, &len)) {
        return -1;
    }
#if SIZEOF_SIZE_T == 4
    if (len > PY_SSIZE_T_MAX) {
        PyErr_SetString(PyExc_OverflowError, "length too big for Python");
        return -1;
    }
#endif
    return len;
}


static PyObject *
_PyJSArray_Concat(JSContext *cx, PyJSObject *self, PyObject *pyother)
{
    PyObject *pyargs = PySequence_Fast(pyother,
                                       "can only concatenate an iterable");
    if (!pyargs) {
        return NULL;
    }
    jsval rv;
    if (!PyJSObject_CallMethodString(cx, self, "concat", &rv, pyargs)) {
        Py_DECREF(pyargs);
        return NULL;
    }
    Py_DECREF(pyargs);
    return PyObject_FromJSVal(cx, rv);
}


/* see _PyJSArray_[Clear/Copy]... (a little bit silly, isn't it?) */
static jsval *jsval_zero = const_cast<jsval *>(&JSVAL_ZERO);


static PyObject *
_PyJSArray_Copy(JSContext *cx, PyJSObject *self)
{
    jsval rv;
    if (!JS_CallFunctionName(cx, self->jsobj, "slice", 1, jsval_zero, &rv)) {
        return NULL;
    }
    return PyObject_FromJSVal(cx, rv);
}


static JSBool
_PyJSArray_Clear(JSContext *cx, PyJSObject *self)
{
    jsval rv;
    return JS_CallFunctionName(cx, self->jsobj, "splice", 1, jsval_zero, &rv);
}


static PyObject *
_PyJSArray_GetItem(JSContext *cx, PyJSObject *self, Py_ssize_t pyidx)
{
    if (pyidx > UINT32_MAX) {
        PyErr_SetString(PyExc_OverflowError, "index too big for JavaScript");
        return NULL;
    }
    JSBool found;
    if (!JS_HasElement(cx, self->jsobj, pyidx, &found)) {
        return NULL;
    }
    if (found) {
        jsval rv;
        if (!JS_GetElement(cx, self->jsobj, pyidx, &rv)) {
            return NULL;
        }
        return PyObject_FromJSVal(cx, rv);
    }
    else {
        PyErr_SetString(PyExc_IndexError, "index out of range");
        return NULL;
    }
}


static int
_PyJSArray_SetItem(JSContext *cx, PyJSObject *self, Py_ssize_t pyidx,
                   PyObject *pyvalue)
{
    if (pyidx > UINT32_MAX) {
        PyErr_SetString(PyExc_OverflowError,
                        "assignment index too big for JavaScript");
        return -1;
    }
    JSBool found;
    if (!JS_HasElement(cx, self->jsobj, pyidx, &found)) {
        return -1;
    }
    if (found) {
        if (pyvalue) {
            jsval argv = PyObject_AsJSVal(cx, pyvalue);
            if (JSVAL_IS_VOID(argv)) {
                return -1;
            }
            JS::AutoValueRooter avr(cx, argv);
            return JS_SetElement(cx, self->jsobj, pyidx, &argv) ? 0 : -1;
        }
        return JS_DeleteElement(cx, self->jsobj, pyidx) ? 0 : -1;
    }
    else  {
        PyErr_SetString(PyExc_IndexError, "assignment index out of range");
        return -1;
    }
}


static JSBool
_PyJSArray_CallSpecial2(JSContext *cx, PyJSObject *self, const char *name,
                        jsval *rv, Py_ssize_t pyarg0, Py_ssize_t pyarg1)
{
    jsval argv[2];
    argv[0] = JS_NumberValue(pyarg0);
    argv[1] = JS_NumberValue(pyarg1);
    JS::AutoArrayRooter aar(cx, 2, argv);
    return JS_CallFunctionName(cx, self->jsobj, name, 2, argv, rv);
}

#define _PyJSArray_Slice(cx, pyjsobj, rv, begin, end) \
    _PyJSArray_CallSpecial2(cx, pyjsobj, "slice", rv, begin, end)

#define _PyJSArray_Splice(cx, pyjsobj, rv, index, howMany) \
    _PyJSArray_CallSpecial2(cx, pyjsobj, "splice", rv, index, howMany)


static PyObject *
_PyJSArray_GetSlice(JSContext *cx, PyJSObject *self, PyObject *pyslice,
                    Py_ssize_t pylength)
{
    Py_ssize_t start, stop, step, slicelength;
    if (PySlice_GetIndicesEx(pyslice, pylength,
                             &start, &stop, &step, &slicelength)) {
        return NULL;
    }
    if (step != 1) {
        PyErr_SetString(PyExc_ValueError, "slice step can only be 1");
        return NULL;
    }
    jsval rv;
    if (!_PyJSArray_Slice(cx, self, &rv, start, stop)) {
        return NULL;
    }
    return PyObject_FromJSVal(cx, rv);
}


static int
_PyJSArray_SetSlice(JSContext *cx, PyJSObject *self, PyObject *pyslice,
                    PyObject *pyvalue, Py_ssize_t pylength)
{
    Py_ssize_t start, stop, step, slicelength;
    if (PySlice_GetIndicesEx(pyslice, pylength,
                             &start, &stop, &step, &slicelength)) {
        return -1;
    }
    if (step != 1) {
        PyErr_SetString(PyExc_ValueError, "slice step can only be 1");
        return -1;
    }
    jsval rv;
    if (pyvalue) {
        PyObject *pyargs = Py_BuildValue("[nn]", start, slicelength);
        if (!pyargs) {
            return -1;
        }
        if (!_PyList_Extend((PyListObject *)pyargs, pyvalue)) {
            Py_DECREF(pyargs);
            return -1;
        }
        Py_DECREF(Py_None);
        int result = PyJSObject_CallMethodString(cx, self, "splice", &rv, pyargs) ? 0 : -1;
        Py_DECREF(pyargs);
        return result;
    }
    return _PyJSArray_Splice(cx, self, &rv, start, slicelength) ? 0 : -1;
}


/* PyJSArray_tp_as_sequence.sq_length */
static Py_ssize_t
PyJSArray_Length(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(-1)
    return _PyJSArray_Length(cx, self);
}


/* PyJSArray_tp_as_sequence.sq_concat */
static PyObject *
PyJSArray_Concat(PyJSObject *self, PyObject *pyother)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return _PyJSArray_Concat(cx, self, pyother);
}


/* PyJSArray_tp_as_sequence.sq_repeat */
static PyObject *
PyJSArray_Repeat(PyJSObject *self, Py_ssize_t pycount)
{
    PYJSOBJECT_PREP_CALL(NULL)
    PyObject *pyresult = _PyJSArray_Copy(cx, self); // self * 1
    if (pyresult) {
        if (pycount < 1) {
            if (!_PyJSArray_Clear(cx, (PyJSObject *)pyresult)) {
                Py_CLEAR(pyresult);
            }
        }
        else if (pycount > 1) {
            for (Py_ssize_t i = 1; i < pycount; i++) {
                if (pyresult) {
                    PyObject *pytmp = pyresult;
                    pyresult = _PyJSArray_Concat(cx, (PyJSObject *)pytmp, self);
                    Py_DECREF(pytmp);
                }
                else {
                    break;
                }
            }
        }
    }
    return pyresult;
}


/* PyJSArray_tp_as_sequence.sq_item */
static PyObject *
PyJSArray_GetItem(PyJSObject *self, Py_ssize_t pyidx)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return _PyJSArray_GetItem(cx, self, pyidx);
}


/* PyJSArray_tp_as_sequence.sq_ass_item */
static int
PyJSArray_SetItem(PyJSObject *self, Py_ssize_t pyidx, PyObject *pyvalue)
{
    PYJSOBJECT_PREP_CALL(-1)
    return _PyJSArray_SetItem(cx, self, pyidx, pyvalue);
}


/* PyJSArray_tp_as_sequence.sq_contains */
static int
PyJSArray_Contains(PyJSObject *self, PyObject *pyvalue)
{
    PYJSOBJECT_PREP_CALL(-1)
    jsval rv;
    jsval argv = PyObject_AsJSVal(cx, pyvalue);
    if (JSVAL_IS_VOID(argv)) {
        return -1;
    }
    JS::AutoValueRooter avr(cx, argv);
    if (!JS_CallFunctionName(cx, self->jsobj, "indexOf", 1, &argv, &rv)) {
        return -1;
    }
    return (JSVAL_TO_INT(rv) > -1) ? 1 : 0;
}


/* PyJSArrayType.tp_as_sequence */
static PySequenceMethods PyJSArray_tp_as_sequence = {
    (lenfunc)PyJSArray_Length,                /*sq_length*/
    (binaryfunc)PyJSArray_Concat,             /*sq_concat*/
    (ssizeargfunc)PyJSArray_Repeat,           /*sq_repeat*/
    (ssizeargfunc)PyJSArray_GetItem,          /*sq_item*/
    0,                                        /*was_sq_slice*/
    (ssizeobjargproc)PyJSArray_SetItem,       /*sq_ass_item*/
    0,                                        /*was_sq_ass_slice*/
    (objobjproc)PyJSArray_Contains,           /*sq_contains*/
    0,                                        /*sq_inplace_concat*/
    0                                         /*sq_inplace_repeat*/
};


/* PyJSArray_tp_as_mapping.mp_subscript */
static PyObject *
PyJSArray_GetSlice(PyJSObject *self, PyObject *pyslice)
{
    PYJSOBJECT_PREP_CALL(NULL)
    Py_ssize_t pylength = _PyJSArray_Length(cx, self);

    if (pylength < 0) {
        return NULL;
    }
    if (PyIndex_Check(pyslice)) {
        Py_ssize_t pyidx = PyNumber_AsSsize_t(pyslice, PyExc_IndexError);
        if (pyidx == -1 && PyErr_Occurred()) {
            return NULL;
        }
        if (pyidx < 0) {
            pyidx += pylength;
        }
        return _PyJSArray_GetItem(cx, self, pyidx);
    }
    else if (PySlice_Check(pyslice)) {
        return _PyJSArray_GetSlice(cx, self, pyslice, pylength);
    }
    else {
        PyErr_SetString(PyExc_TypeError, "indices must be integers");
        return NULL;
    }
}


/* PyJSArray_tp_as_mapping.mp_ass_subscript */
static int
PyJSArray_SetSlice(PyJSObject *self, PyObject *pyslice, PyObject *pyvalue)
{
    PYJSOBJECT_PREP_CALL(-1)
    Py_ssize_t pylength = _PyJSArray_Length(cx, self);
    if (pylength < 0) {
        return -1;
    }
    if (PyIndex_Check(pyslice)) {
        Py_ssize_t pyidx = PyNumber_AsSsize_t(pyslice, PyExc_IndexError);
        if (pyidx == -1 && PyErr_Occurred()) {
            return -1;
        }
        if (pyidx < 0) {
            pyidx += pylength;
        }
        return _PyJSArray_SetItem(cx, self, pyidx, pyvalue);
    }
    else if (PySlice_Check(pyslice)) {
        return _PyJSArray_SetSlice(cx, self, pyslice, pyvalue, pylength);
    }
    else {
        PyErr_SetString(PyExc_TypeError, "indices must be integers");
        return -1;
    }
}


/* PyJSArrayType.tp_as_mapping */
static PyMappingMethods PyJSArray_tp_as_mapping = {
    (lenfunc)PyJSArray_Length,                /*mp_length*/
    (binaryfunc)PyJSArray_GetSlice,           /*mp_subscript*/
    (objobjargproc)PyJSArray_SetSlice         /*mp_ass_subscript*/
};


/* PyJSArrayType.tp_iter */
static PyObject *
PyJSArray_tp_iter(PyJSObject *self)
{
    PYJSOBJECT_PREP_CALL(NULL)
    return PyJSObject_GetIter(cx, self, 1);
}


/* PyJSArrayType */
static PyTypeObject PyJSArrayType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "PyJSArray",                              /*tp_name*/
    sizeof(PyJSObject),                       /*tp_basicsize*/
    0,                                        /*tp_itemsize*/
    0,                                        /*tp_dealloc*/
    0,                                        /*tp_print*/
    0,                                        /*tp_getattr*/
    0,                                        /*tp_setattr*/
    0,                                        /*tp_compare*/
    0,                                        /*tp_repr*/
    0,                                        /*tp_as_number*/
    &PyJSArray_tp_as_sequence,                /*tp_as_sequence*/
    &PyJSArray_tp_as_mapping,                 /*tp_as_mapping*/
    0,                                        /*tp_hash */
    0,                                        /*tp_call*/
    0,                                        /*tp_str*/
    0,                                        /*tp_getattro*/
    0,                                        /*tp_setattro*/
    0,                                        /*tp_as_buffer*/
    0,                                        /*tp_flags*/
    0,                                        /*tp_doc*/
    0,                                        /*tp_traverse*/
    0,                                        /*tp_clear*/
    0,                                        /*tp_richcompare*/
    0,                                        /*tp_weaklistoffset*/
    (getiterfunc)PyJSArray_tp_iter,           /*tp_iter*/
    0,                                        /*tp_iternext*/
    0,                                        /*tp_methods*/
    0,                                        /*tp_members*/
    0,                                        /*tp_getset*/
    0,/*&PyJSObjectType,*/                          /*tp_base*/
};


/*******************************************************************************
 PyJSFunctionType
*******************************************************************************/

/* PyJSFunctionType.tp_dealloc */
static void
PyJSFunction_tp_dealloc(PyJSFunction *self)
{
    if (self->that) {
        pyRuntime::sPyRuntime->DropJSObject(&self->that);
        self->that = NULL;
    }
    PyJSObjectType.tp_dealloc(self);
}


/* PyJSFunctionType.tp_call */
static PyObject *
PyJSFunction_tp_call(PyJSFunction *self, PyObject *pyargs, PyObject *pykwargs)
{
    PYJSOBJECT_PREP_CALL(NULL)
    if (pykwargs && PyDict_Size(pykwargs) != 0) {
        PyErr_SetString(PyExc_TypeError,
                        "JSFunction do not take named arguments");
        return NULL;
    }
    jsval rv;
    JSBool result = JS_FALSE;
    JSObject *that =
        self->that ? self->that : JS_GetGlobalForObject(cx, self->jsobj);
    unsigned argc = 0;
    jsval *argv = PyArgs_AsJSArgs(cx, pyargs, &argc);
    if (!argv && PyErr_Occurred()) {
        return NULL;
    }
    result = JS_CallFunctionValue(cx, that, OBJECT_TO_JSVAL(self->jsobj), argc,
                                  argv, &rv);
    if (argv) {
        JSArgs_Destroy(cx, argv, argc);
    }
    if (!result) {
        return NULL;
    }
    if (JSVAL_IS_VOID(rv)) {
        Py_RETURN_NONE;
    }
    return PyObject_FromJSVal(cx, rv);
}


/* PyJSFunctionType */
static PyTypeObject PyJSFunctionType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "PyJSFunction",                           /*tp_name*/
    sizeof(PyJSFunction),                     /*tp_basicsize*/
    0,                                        /*tp_itemsize*/
    (destructor)PyJSFunction_tp_dealloc,      /*tp_dealloc*/
    0,                                        /*tp_print*/
    0,                                        /*tp_getattr*/
    0,                                        /*tp_setattr*/
    0,                                        /*tp_compare*/
    0,                                        /*tp_repr*/
    0,                                        /*tp_as_number*/
    0,                                        /*tp_as_sequence*/
    0,                                        /*tp_as_mapping*/
    0,                                        /*tp_hash */
    (ternaryfunc)PyJSFunction_tp_call,        /*tp_call*/
    0,                                        /*tp_str*/
    0,                                        /*tp_getattro*/
    0,                                        /*tp_setattro*/
    0,                                        /*tp_as_buffer*/
    0,                                        /*tp_flags*/
    0,                                        /*tp_doc*/
    0,                                        /*tp_traverse*/
    0,                                        /*tp_clear*/
    0,                                        /*tp_richcompare*/
    0,                                        /*tp_weaklistoffset*/
    0,                                        /*tp_iter*/
    0,                                        /*tp_iternext*/
    0,                                        /*tp_methods*/
    0,                                        /*tp_members*/
    0,                                        /*tp_getset*/
    0,/*&PyJSObjectType,*/                          /*tp_base*/
};


static bool
PyJSFunction_Check(PyObject *pyobj)
{
    return PyObject_TypeCheck(pyobj, &PyJSFunctionType);
}


static bool
PyJSFunction_SetMethod(PyJSFunction *self, JSObject *that)
{
    if (self->that) {
        pyRuntime::sPyRuntime->DropJSObject(&self->that);
    }
    self->that = that;
    if (!pyRuntime::sPyRuntime->HoldJSObject(&self->that)) {
        self->that = NULL;
        return false;
    }
    return true;
}


/*******************************************************************************
 helpers
*******************************************************************************/

static PyObject *
PyJSObject_Create(PyTypeObject *type, JSObject *jsobj)
{
    PyJSObject *self = (PyJSObject *)type->tp_alloc(type, 0);
    if (!self) {
        return NULL;
    }
    self->jsobj = jsobj;
    if (!pyRuntime::sPyRuntime->HoldJSObject(&self->jsobj)) {
        self->jsobj = NULL;
        Py_DECREF(self);
        return NULL;
    }
    self->weakrefslist = NULL;
    return (PyObject *)self;
}


static PyObject *
PyJSObject_New(JSContext *cx, JSObject *jsobj)
{
    if (JS_ObjectIsFunction(cx, jsobj) || JS_ObjectIsCallable(cx, jsobj)) {
        return PyJSObject_Create(&PyJSFunctionType, jsobj);
    }
    if (JS_IsArrayObject(cx, jsobj)) {
        return PyJSObject_Create(&PyJSArrayType, jsobj);
    }
    if (JSMap_Check(cx, jsobj)) {
        return PyJSObject_Create(&PyJSMapType, jsobj);
    }
    if (JSIterator_Check(cx, jsobj)) {
        return PyJSObject_Create(&PyJSIteratorType, jsobj);
    }
    return PyJSObject_Create(&PyJSObjectType, jsobj);
}


/*******************************************************************************
 exposed
*******************************************************************************/

JSObject *
PyJSObject_GetPrivate(PyObject *pyobj)
{
    return ((PyJSObject *)pyobj)->jsobj;
}


bool
PyJSObject_InitTypes()
{
    PyJSIteratorType.tp_base = &PyJSObjectType;
    PyJSMapType.tp_base = &PyJSObjectType;
    PyJSArrayType.tp_base = &PyJSObjectType;
    PyJSFunctionType.tp_base = &PyJSObjectType;
    if (PyType_Ready(&PyJSObjectType) ||
        PyType_Ready(&PyJSIteratorType) ||
        PyType_Ready(&PyJSMapType) ||
        PyType_Ready(&PyJSArrayType) ||
        PyType_Ready(&PyJSFunctionType)) {
        return false;
    }
    return true;
}


bool
PyJSObject_Check(PyObject *pyobj)
{
    return PyObject_TypeCheck(pyobj, &PyJSObjectType);
}


/*PyObject *
PyJSObject_NewOrUsed(JSContext *cx, JSObject *jsobj)
{
    if (!cx || !jsobj) {
        PyErr_BadArgument();
        return NULL;
    }
    if (JSPyObject_Check(jsobj)) {
        PyObject *pyobj = (PyObject *)JS_GetPrivate(jsobj);
        Py_XINCREF(pyobj);
        return pyobj;

    }
    return PyJSObject_New(cx, jsobj);;
}*/


PyObject *
PyJSObject_NewOrUsed(JSContext *cx, JSObject *jsobj)
{
    if (!cx || !jsobj) {
        PyErr_BadArgument();
        return NULL;
    }
    PyObject *pyobj = NULL;
    if (JSPyObject_Check(jsobj)) {
        pyobj = (PyObject *)JS_GetPrivate(jsobj);
    }
    else if (!pyRuntime::sPyRuntime->GetObject(jsobj, &pyobj)) {
        pyobj = PyJSObject_New(cx, jsobj);
        if (pyobj) {
             pyRuntime::sPyRuntime->PutObject(jsobj, pyobj);
        }
    }
    Py_XINCREF(pyobj);
    return pyobj;
}
